/*
 * Copyright (c) 2012, marco.tamburelli@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.bmi.gwt.ws.rebind;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.bmi.gwt.ws.client.serialization.ClientPayloadSerializer;
import org.bmi.gwt.ws.rebind.util.BlacklistTypeFilter;
import org.bmi.gwt.ws.rebind.util.SerializableTypeOracleBuilder;
import org.bmi.gwt.ws.rebind.util.SerializationUtils;
import org.bmi.gwt.ws.rebind.util.Shared;
import org.bmi.gwt.ws.shared.PayloadSerializer;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.impl.Impl;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.CachedPropertyInformation;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
import com.google.gwt.core.ext.linker.GeneratedResource;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.generator.NameFactory;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
import com.google.gwt.user.linker.rpc.RpcLogArtifact;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.rebind.rpc.CachedRpcTypeInformation;
import com.google.gwt.user.rebind.rpc.ProxyCreator;
import com.google.gwt.user.rebind.rpc.SerializableTypeOracle;
import com.google.gwt.user.rebind.rpc.TypeSerializerCreator;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator;

/**
 * Creator class of the type serializer. Mostly copied from GWT internal code.
 * 
 * @author Marco Tamburelli
 */
public class PayloadSerializerCreator
{
	/**
	 * Some keys for storing cached information for use with generator result
	 * caching. Copied from internal GWT code.
	 */
	public static final String CACHED_PROPERTY_INFO_KEY = "cached-property-info";
	public static final String CACHED_TYPE_INFO_KEY = "cached-type-info";

	/**
	 * The directory within which RPC manifests are placed for individual
	 * permutations.
	 */
	public static final String MANIFEST_ARTIFACT_DIR = "wsPolicyManifest/manifests";

	private String generatedSuffix;

	private JClassType serializerIntf;
	private JClassType typeToSerialize;

	private boolean elideTypeNames;

	/**
	 * Properties which need to be checked to determine cache reusability.
	 */
	private static final Collection<String> configPropsToCheck = Arrays.asList(
			TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC, Shared.WS_ENHANCED_CLASSES);
	private static final Collection<String> selectionPropsToCheck = Arrays
			.asList(Shared.WS_PROP_SUPPRESS_NON_STATIC_FINAL_FIELD_WARNINGS);

	/**
	 * The possibly obfuscated type signatures used to represent a type.
	 */
	private Map<JType, String> typeStrings;

	private Set<JType> customSerializersUsed;
	private Set<JType> typesNotUsingCustomSerializers;

	public PayloadSerializerCreator(JClassType serializerIntf)
	{
		assert (serializerIntf.isInterface() != null);
		this.serializerIntf = serializerIntf;
	}

	/**
	 * Creates a payload serializer instance.
	 * 
	 * @throws UnableToCompleteException
	 */
	public RebindResult create(TreeLogger logger, GeneratorContext context) throws UnableToCompleteException
	{
		TypeOracle typeOracle = context.getTypeOracle();

		JClassType[] superInterface = this.serializerIntf.getImplementedInterfaces();

		if (superInterface.length != 1)
		{
			logger.log(TreeLogger.ERROR, this.serializerIntf.getQualifiedSourceName() + " can only extends "
					+ PayloadSerializer.class.getCanonicalName());
			throw new UnableToCompleteException();
		}

		JParameterizedType parametrizedType = superInterface[0].isParameterized();

		assert (parametrizedType != null);

		this.typeToSerialize = parametrizedType.getTypeArgs()[0];

		this.generatedSuffix = "_" + this.typeToSerialize.getName() + "_" + "__Impl";

		SourceWriter srcWriter = getSourceWriter(logger, context, this.serializerIntf);
		if (srcWriter == null) { return new RebindResult(RebindMode.USE_EXISTING, getQualifiedName()); }

		final PropertyOracle propertyOracle = context.getPropertyOracle();

		/* Load the blacklist/whitelist */
		BlacklistTypeFilter blacklistTypeFilter = new BlacklistTypeFilter(logger, propertyOracle);

		/* Determine the set of serializable types */
		Event event = SpeedTracerLogger.start(CompilerEventType.GENERATOR_RPC_STOB);

		SerializableTypeOracleBuilder typesSentFromBrowserBuilder = new SerializableTypeOracleBuilder(logger,
				propertyOracle, context);
		typesSentFromBrowserBuilder.setTypeFilter(blacklistTypeFilter);
		SerializableTypeOracleBuilder typesSentToBrowserBuilder = new SerializableTypeOracleBuilder(logger,
				propertyOracle, context);
		typesSentToBrowserBuilder.setTypeFilter(blacklistTypeFilter);

		addRoots(logger, typeOracle, typesSentFromBrowserBuilder, typesSentToBrowserBuilder);

		try
		{
			ConfigurationProperty prop = context.getPropertyOracle().getConfigurationProperty(
					TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC);
			this.elideTypeNames = Boolean.parseBoolean(prop.getValues().get(0));
		}
		catch (BadPropertyValueException e)
		{
			logger.log(TreeLogger.ERROR, "Configuration property "
					+ TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC
					+ " is not defined. Is RemoteService.gwt.xml inherited?");
			throw new UnableToCompleteException();
		}

		/*
		 * Decide what types to send in each direction. Log the decisions to a
		 * string that will be written later in this method
		 */
		SerializableTypeOracle typesSentFromBrowser;
		SerializableTypeOracle typesSentToBrowser;
		String rpcLog;
		{
			StringWriter stringWriter = new StringWriter();
			PrintWriter writer = new PrintWriter(stringWriter);

			typesSentFromBrowserBuilder.setLogOutputWriter(writer);
			typesSentToBrowserBuilder.setLogOutputWriter(writer);

			writer.write("====================================\n");
			writer.write("Types potentially sent from browser:\n");
			writer.write("====================================\n\n");
			writer.flush();
			typesSentFromBrowser = typesSentFromBrowserBuilder.build(logger);

			writer.write("===================================\n");
			writer.write("Types potentially sent from server:\n");
			writer.write("===================================\n\n");
			writer.flush();
			typesSentToBrowser = typesSentToBrowserBuilder.build(logger);

			writer.close();
			rpcLog = stringWriter.toString();
		}
		event.end();

		generateTypeHandlers(logger, context, typesSentFromBrowser, typesSentToBrowser);

		String serializationPolicyStrongName = writeSerializationPolicyFile(logger, context, typesSentFromBrowser,
				typesSentToBrowser);

		generateFields(srcWriter, typesSentFromBrowser, serializationPolicyStrongName);
		generateContructor(srcWriter);

		generateMethods(srcWriter, typesSentFromBrowser, typeOracle);

		srcWriter.commit(logger);

		if (context.isProdMode() || logger.isLoggable(TreeLogger.DEBUG))
		{
			/*
			 * Create an artifact explaining STOB's decisions. It will be
			 * emitted by RpcLogLinker
			 */
			context.commitArtifact(logger, new RpcLogArtifact(this.serializerIntf.getQualifiedSourceName(),
					serializationPolicyStrongName, rpcLog));
		}

		if (checkGeneratorResultCacheability(context))
		{
			/*
			 * Create a new cacheable result. The mode is set to
			 * RebindMode.USE_PARTIAL_CACHED, since we are allowing reuse of
			 * cached results for field serializers, when available, but all
			 * other types have been newly generated.
			 */
			RebindResult result = new RebindResult(RebindMode.USE_PARTIAL_CACHED, getQualifiedName());

			/*
			 * Remember the type info that we care about for cache reuse
			 * testing.
			 */
			CachedRpcTypeInformation cti = new CachedRpcTypeInformation(typesSentFromBrowser, typesSentToBrowser,
					customSerializersUsed, typesNotUsingCustomSerializers);
			CachedPropertyInformation cpi = new CachedPropertyInformation(logger, context.getPropertyOracle(),
					selectionPropsToCheck, configPropsToCheck);
			result.putClientData(CACHED_TYPE_INFO_KEY, cti);
			result.putClientData(CACHED_PROPERTY_INFO_KEY, cpi);

			return result;
		}
		else
		{
			return new RebindResult(RebindMode.USE_ALL_NEW_WITH_NO_CACHING, getQualifiedName());
		}
	}

	protected void addRoots(TreeLogger logger, TypeOracle typeOracle,
			SerializableTypeOracleBuilder typesSentFromBrowserBuilder,
			SerializableTypeOracleBuilder typesSentToBrowserBuilder) throws UnableToCompleteException
	{
		try
		{
			TypesSerializationUtil.addRequiredRoots(logger, typeOracle, typesSentFromBrowserBuilder);
			TypesSerializationUtil.addRequiredRoots(logger, typeOracle, typesSentToBrowserBuilder);

			TypesSerializationUtil.addRemoteServiceRootTypes(logger, typeOracle, typesSentFromBrowserBuilder,
					typesSentToBrowserBuilder, this.typeToSerialize);
		}
		catch (NotFoundException e)
		{
			logger.log(TreeLogger.ERROR, "Unable to find type referenced from remote service", e);
			throw new UnableToCompleteException();
		}
	}

	protected String computeTypeNameExpression(JType paramType)
	{
		String typeName;

		if (this.typeStrings.containsKey(paramType))
			typeName = typeStrings.get(paramType);
		else
			typeName = SerializationUtils.getRpcTypeName(paramType);

		return typeName == null ? null : ('"' + typeName + '"');
	}

	/**
	 * Generate the constructor and delegate to the superclass constructor.
	 */
	protected void generateContructor(SourceWriter srcWriter)
	{
		srcWriter.println("public " + getSimpleName() + "() {");
		srcWriter.indent();
		srcWriter.println("super(GWT.getModuleBaseURL(),");
		srcWriter.indent();
		srcWriter.println("SERIALIZATION_POLICY, ");
		srcWriter.println("SERIALIZER);");
		srcWriter.outdent();
		srcWriter.outdent();
		srcWriter.println("}");
	}

	/**
	 * Generate any fields required by the proxy.
	 * 
	 * @param serializableTypeOracle the type oracle
	 */
	protected void generateFields(SourceWriter srcWriter, SerializableTypeOracle serializableTypeOracle,
			String serializationPolicyStrongName)
	{
		srcWriter.println("private static final String SERIALIZATION_POLICY =\"" + serializationPolicyStrongName
				+ "\";");
		String typeSerializerName = SerializationUtils.getTypeSerializerQualifiedName(this.serializerIntf);

		srcWriter.println("private static final " + typeSerializerName + " SERIALIZER = new " + typeSerializerName
				+ "();");
		srcWriter.println();
	}

	private void generateDeserializeMethod(SourceWriter w, SerializableTypeOracle serializableTypeOracle,
			TypeOracle typeOracle)
	{
		NameFactory nameFactory = new NameFactory();
		String payload = nameFactory.createName("payload");

		w.println();

		w.println("public %s deserialize(String %s) throws SerializationException {", this.typeToSerialize.getName(),
				payload);
		w.indent();

		String streamReader = nameFactory.createName("streamReader");

		w.println("%s %s = createStreamReader(%s);", SerializationStreamReader.class.getSimpleName(), streamReader,
				payload);

		w.println();

		w.println("return (%s) %s.%s();", this.typeToSerialize.getName(), streamReader,
				Shared.getStreamReadMethodNameFor(this.typeToSerialize));
		w.outdent();

		w.println("}");
	}

	private void generateSerializeMethod(SourceWriter w, SerializableTypeOracle serializableTypeOracle,
			TypeOracle typeOracle)
	{
		NameFactory nameFactory = new NameFactory();
		String obj = nameFactory.createName("obj");

		w.println();

		w.println("public String serialize(%s %s) throws SerializationException {", this.typeToSerialize.getName(), obj);
		w.indent();

		String streamWriter = nameFactory.createName("streamWriter");

		w.println("%s %s = createStreamWriter();", SerializationStreamWriter.class.getSimpleName(), streamWriter);

		String typeNameExpression = computeTypeNameExpression(this.typeToSerialize);
		assert typeNameExpression != null : "Could not compute a type name for "
				+ this.typeToSerialize.getQualifiedSourceName();

		w.println("%s.writeString(%s);", streamWriter, typeNameExpression);

		w.println("%s.%s(%s);", streamWriter, Shared.getStreamWriteMethodNameFor(this.typeToSerialize), obj);

		w.println();

		w.println("return %s.toString();", streamWriter);

		w.outdent();

		w.println("}");
	}

	protected void generateMethods(SourceWriter w, SerializableTypeOracle serializableTypeOracle, TypeOracle typeOracle)
	{
		generateSerializeMethod(w, serializableTypeOracle, typeOracle);
		generateDeserializeMethod(w, serializableTypeOracle, typeOracle);
	}

	/**
	 * @param syncMethod
	 * @param asyncMethod
	 * @param statsContextName
	 */
	protected void generateRpcStatsContext(SourceWriter w, JMethod syncMethod, JMethod asyncMethod,
			String statsContextName)
	{
		w.println("RpcStatsContext " + statsContextName + " = new RpcStatsContext();");
	}

	protected void generateStreamWriterOverride(SourceWriter srcWriter)
	{
		srcWriter.println("@Override");
		srcWriter.println("public SerializationStreamWriter createStreamWriter() {");
		srcWriter.indent();

		/*
		 * Need an explicit cast since we've widened the declaration of the
		 * method in RemoteServiceProxy.
		 */
		srcWriter.println("ClientSerializationStreamWriter toReturn =");
		srcWriter.indentln("(ClientSerializationStreamWriter) super.createStreamWriter();");

		if (this.elideTypeNames)
			srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter." + "FLAG_ELIDE_TYPE_NAMES);");

		srcWriter.println("if (getRpcToken() != null) {");
		srcWriter.indent();
		srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter." + "FLAG_RPC_TOKEN_INCLUDED);");
		srcWriter.outdent();
		srcWriter.println("}");
		srcWriter.println("return toReturn;");
		srcWriter.outdent();
		srcWriter.println("}");
	}

	protected void generateTypeHandlers(TreeLogger logger, GeneratorContext context,
			SerializableTypeOracle typesSentFromBrowser, SerializableTypeOracle typesSentToBrowser)
			throws UnableToCompleteException
	{
		Event event = SpeedTracerLogger.start(CompilerEventType.GENERATOR_RPC_TYPE_SERIALIZER);
		TypeSerializerCreator tsc = new TypeSerializerCreator(logger, typesSentFromBrowser, typesSentToBrowser,
				context, SerializationUtils.getTypeSerializerQualifiedName(this.serializerIntf),
				SerializationUtils.getTypeSerializerSimpleName(this.serializerIntf));
		tsc.realize(logger);
		event.end();

		typeStrings = new HashMap<JType, String>(tsc.getTypeStrings());
		typeStrings.put(this.serializerIntf, TypeNameObfuscator.SERVICE_INTERFACE_ID);

		customSerializersUsed = tsc.getCustomSerializersUsed();
		typesNotUsingCustomSerializers = tsc.getTypesNotUsingCustomSerializers();
	}

	/**
	 * Returns the simple name of the class being generated.
	 * 
	 * @return
	 */
	protected String getSimpleName()
	{
		String[] name = Shared.synthesizeTopLevelClassName(this.serializerIntf, generatedSuffix);
		return name[1];
	}

	protected Class<? extends ClientPayloadSerializer> getRequiredSupertype()
	{
		return ClientPayloadSerializer.class;
	}

	protected Class<? extends SerializationStreamWriter> getStreamWriterClass()
	{
		return ClientSerializationStreamWriter.class;
	}

	protected String writeSerializationPolicyFile(TreeLogger logger, GeneratorContext ctx,
			SerializableTypeOracle serializationSto, SerializableTypeOracle deserializationSto)
			throws UnableToCompleteException
	{
		try
		{
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			OutputStreamWriter osw = new OutputStreamWriter(baos,
					SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
			TypeOracle oracle = ctx.getTypeOracle();
			PrintWriter pw = new PrintWriter(osw);

			JType[] serializableTypes = unionOfTypeArrays(serializationSto.getSerializableTypes(),
					deserializationSto.getSerializableTypes(), new JType[] { this.serializerIntf });

			for (int i = 0; i < serializableTypes.length; ++i)
			{
				JType type = serializableTypes[i];
				String binaryTypeName = SerializationUtils.getRpcTypeName(type);
				pw.print(binaryTypeName);
				pw.print(", " + Boolean.toString(deserializationSto.isSerializable(type)));
				pw.print(", " + Boolean.toString(deserializationSto.maybeInstantiated(type)));
				pw.print(", " + Boolean.toString(serializationSto.isSerializable(type)));
				pw.print(", " + Boolean.toString(serializationSto.maybeInstantiated(type)));
				pw.print(", " + typeStrings.get(type));

				/*
				 * Include the serialization signature to bump the RPC file name
				 * if obfuscated identifiers are used.
				 */
				pw.print(", " + SerializationUtils.getSerializationSignature(oracle, type));
				pw.print('\n');

				/*
				 * Emit client-side field information for classes that may be
				 * enhanced on the server. Each line consists of a
				 * comma-separated list containing the keyword '@ClientFields',
				 * the class name, and a list of all potentially serializable
				 * client-visible fields.
				 */
				if ((type instanceof JClassType) && ((JClassType) type).isEnhanced())
				{
					JField[] fields = ((JClassType) type).getFields();
					JField[] rpcFields = new JField[fields.length];
					int numRpcFields = 0;
					for (JField f : fields)
					{
						if (f.isTransient() || f.isStatic() || f.isFinal())
						{
							continue;
						}
						rpcFields[numRpcFields++] = f;
					}

					pw.print(SerializationPolicyLoader.CLIENT_FIELDS_KEYWORD);
					pw.print(',');
					pw.print(binaryTypeName);
					for (int idx = 0; idx < numRpcFields; idx++)
					{
						pw.print(',');
						pw.print(rpcFields[idx].getName());
					}
					pw.print('\n');
				}
			}

			// Closes the wrapped streams.
			pw.close();

			byte[] serializationPolicyFileContents = baos.toByteArray();
			String serializationPolicyName = Util.computeStrongName(serializationPolicyFileContents);

			String serializationPolicyFileName = SerializationPolicyLoader
					.getSerializationPolicyFileName(serializationPolicyName);
			OutputStream os = ctx.tryCreateResource(logger, serializationPolicyFileName);
			if (os != null)
			{
				os.write(serializationPolicyFileContents);
				GeneratedResource resource = ctx.commitResource(logger, os);

				/*
				 * Record which proxy class created the resource. A manifest
				 * will be emitted by the RpcPolicyManifestLinker.
				 */
				emitPolicyFileArtifact(logger, ctx, resource.getPartialPath());
			}
			else
			{
				if (logger.isLoggable(TreeLogger.TRACE))
				{
					logger.log(
							TreeLogger.TRACE,
							"SerializationPolicy file for PayloadSerializer '"
									+ this.serializerIntf.getQualifiedSourceName()
									+ "' already exists; no need to rewrite it.", null);
				}
			}

			return serializationPolicyName;
		}
		catch (UnsupportedEncodingException e)
		{
			logger.log(TreeLogger.ERROR, SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING
					+ " is not supported", e);
			throw new UnableToCompleteException();
		}
		catch (IOException e)
		{
			logger.log(TreeLogger.ERROR, null, e);
			throw new UnableToCompleteException();
		}
	}

	private boolean checkGeneratorResultCacheability(GeneratorContext context)
	{
		/*
		 * Currently not supporting caching for implementations which sub-class
		 * this class, such as {@link RpcProxyCreator}, which implements deRPC.
		 */
		if (!this.getClass().equals(ProxyCreator.class)) { return false; }

		return context.isGeneratorResultCachingEnabled();
	}

	private void emitPolicyFileArtifact(TreeLogger logger, GeneratorContext context, String partialPath)
			throws UnableToCompleteException
	{
		try
		{
			String qualifiedSourceName = this.serializerIntf.getQualifiedSourceName();
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			Writer writer;
			writer = new OutputStreamWriter(baos, SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
			writer.write("serviceClass: " + qualifiedSourceName + "\n");
			writer.write("path: " + partialPath + "\n");
			writer.close();

			byte[] manifestBytes = baos.toByteArray();
			String md5 = Util.computeStrongName(manifestBytes);
			OutputStream os = context.tryCreateResource(logger, MANIFEST_ARTIFACT_DIR + "/" + md5 + ".txt");
			os.write(manifestBytes);

			GeneratedResource resource = context.commitResource(logger, os);
			// TODO: change to Deploy when possible
			resource.setVisibility(Visibility.LegacyDeploy);
		}
		catch (UnsupportedEncodingException e)
		{
			logger.log(TreeLogger.ERROR, SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING
					+ " is not supported", e);
			throw new UnableToCompleteException();
		}
		catch (IOException e)
		{
			logger.log(TreeLogger.ERROR, null, e);
			throw new UnableToCompleteException();
		}
	}

	private String getQualifiedName()
	{
		String[] name = Shared.synthesizeTopLevelClassName(this.serializerIntf, this.generatedSuffix);
		return name[0].length() == 0 ? name[1] : name[0] + "." + name[1];
	}

	private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx, JClassType targetType)
	{
		JPackage serviceIntfPkg = targetType.getPackage();
		String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
		PrintWriter printWriter = ctx.tryCreate(logger, packageName, getSimpleName());
		if (printWriter == null) { return null; }

		ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName,
				getSimpleName());

		String[] imports = new String[] { this.typeToSerialize.getQualifiedSourceName(),
				getRequiredSupertype().getCanonicalName(), getStreamWriterClass().getCanonicalName(),
				SerializationStreamWriter.class.getCanonicalName(), SerializationStreamReader.class.getCanonicalName(),
				GWT.class.getCanonicalName(), ResponseReader.class.getCanonicalName(),
				SerializationException.class.getCanonicalName(), Impl.class.getCanonicalName() };

		for (String imp : imports)
			composerFactory.addImport(imp);

		composerFactory.setSuperclass(getRequiredSupertype().getSimpleName());
		composerFactory.addImplementedInterface(targetType.getErasedType().getQualifiedSourceName());

		return composerFactory.createSourceWriter(ctx, printWriter);
	}

	/**
	 * Take the union of two type arrays, and then sort the results
	 * alphabetically.
	 */
	private static JType[] unionOfTypeArrays(JType[]... types)
	{
		Set<JType> typesList = new HashSet<JType>();
		for (JType[] a : types)
		{
			typesList.addAll(Arrays.asList(a));
		}
		JType[] serializableTypes = typesList.toArray(new JType[0]);
		Arrays.sort(serializableTypes, new Comparator<JType>()
		{
			@Override
			public int compare(JType t1, JType t2)
			{
				return t1.getQualifiedSourceName().compareTo(t2.getQualifiedSourceName());
			}
		});
		return serializableTypes;
	}
}
